﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.CompilerServices;

using LRSTaxServices.DAL;
using LRSTaxServices.DAL.dsSubAdminTableAdapters;
using LRSTaxServices.Common;

namespace LRSTaxServices.Biz
{
    /// <summary>
    /// Sub Admin Business Object
    /// </summary>

    public class SubAdmin : BOBase
    {
        #region Types

        #region RBS Error Enums

        public enum RbsError : int
        {
            /// <summary>
            /// No error detected.
            /// </summary>
            NO_ERROR = 0,

            /// <summary>
            /// Username may not be NULL nor zero-length.
            /// </summary>
            INVALID_USERNAME,

            /// <summary>
            /// The UserID supplied does not exist in the RBS database.
            /// </summary>
            INVALID_USERID,

            /// <summary>
            /// The supplied username/password do not match any RBS users.
            /// </summary>
            INVALID_CREDENTIALS,

            /// <summary>
            /// The username is valid; the password is expired; logon failed.
            /// </summary>
            PASSWORD_CHANGE_REQUIRED,

            /// <summary>
            /// The user is shown as already logged-on and Prohibit Concurrent Logins is in effect for this user.
            /// </summary>
            ALREADY_LOGGED_ON,

            /// <summary>
            /// The specified user account is locked out.
            /// </summary>
            ACCOUNT_LOCKED_OUT,

            /// <summary>
            /// The specified user account has expired.
            /// </summary>
            ACCOUNT_EXPIRED,

            /// <summary>
            /// The specified user account is not active.
            /// </summary>
            ACCOUNT_INACTIVE,

            /// <summary>
            /// The specified username already exists in the RBS
            /// </summary>
            ALREADY_EXISTS,

            /// <summary>
            /// No database records were found
            /// </summary>
            NO_RECORDS_FOUND,

            /// <summary>
            /// A database constraint violation was detected.
            /// </summary>
            CONSTRAINT_VIOLATION,

            /// <summary>
            /// The called function requires the caller to be authenticated.
            /// </summary>
            CALLER_NOT_AUTHENTICATED,

            /// <summary>
            /// An unknown exception has occurred.
            /// </summary>
            OTHER_EXCEPTION,

            /// <summary>
            /// One or more function parameters were invalid as a result of being null.
            /// </summary>
            INVALID_NULL_PARAMETER,

            /// <summary>
            /// The specified password is either null or does not meet the application's length requirements.
            /// </summary>
            PASSWORD_TOO_SHORT,

            /// <summary>
            /// The specified company is non-production/pending and the user is NOT a Site Admin.
            /// </summary>
            ACCOUNT_PENDING,

            /// <summary>
            /// The specified user has no associated roles.
            /// </summary>
            USER_NO_ROLES
        }

        #endregion

        #endregion

        #region Constants

        // Name of the session variable that holds the SubAdmin object for the currently logged-on user.
        public static readonly string PROP_CURRENT = "__AppUser::Current";

        // Role-based Security Constants
        private static readonly string APPUSER_DFLT_PSWDSALT = "_5hW)+32Kdu*@#%Djd?3~j45";
        private static readonly int APPUSER_DFLT_MINPSWDLEN = 6;

        // Log file for ORM exceptions, etc.
        private const string LOG_FILE = "~/logs/HAC.Biz.AppUser.log";

        #endregion

        #region Construction / Disposal

		/// <summary>
		/// Default constructor.
		/// </summary>
		protected SubAdmin() { }

		#endregion

        #region Public Properties

        #region Static Properties

        /// <summary>
        /// The SubAdmin object representing the currently authenticated user.
        /// </summary>
        public static SubAdmin Current
        {
            get
            {
                if (SessionMgr.Session[PROP_CURRENT] == null)
                {
                    lock (typeof(SubAdmin))
                    {
                        if (SessionMgr.Session[PROP_CURRENT] == null)
                            SessionMgr.Session[PROP_CURRENT] = new SubAdmin();
                    }
                }
                return ((SubAdmin)SessionMgr.Session[PROP_CURRENT]);
            }
        }

        #endregion

        #region Instance Properties

        /// <summary>
        /// PKID of the SubAdmin.
        /// </summary>
        public int UserID
        {
            get { return _userID; }
            set
            {
                if (_userID != value)
                {
                    if (OnPropertyChanging("UserID", _userID, value))
                    {
                        int oldValue = _userID;
                        _userID = value;

                        if (!SuppressClearRolesOnUserIDChanged)
                            //_roles = null;

                        OnPropertyChanged("UserID", oldValue, value);
                    }
                }
            }
        }  private int _userID = -1;

        /// <summary>
        /// Alternate key: GUID associated with this UserID. Use this alternate key in URL query strings
        /// and/or when importing/exporting data; never expose the UserID outside of the application.
        /// </summary>
        public Guid UserGUID
        {
            get { return (_userGUID); }
            set { _userGUID = value; }
        } private Guid _userGUID = Guid.Empty;


        /// <summary>
        /// If true, this object will not clear the Roles collection when the UserID is changed.
        /// This is intended to be used when creating new users and roles simultaneously.
        /// Changes to this property do *not* fire the OnPropertyChanging / OnPropertyChanged events.
        /// </summary>
        public bool SuppressClearRolesOnUserIDChanged
        {
            get { return (_suppressClearRolesOnUserIDChanged); }
            set { _suppressClearRolesOnUserIDChanged = value; }
        } private bool _suppressClearRolesOnUserIDChanged = false;


        /// <summary>
        /// [Read-only] String representation of the User's Login.
        /// Business Rule: UserLogin = UserLogin. To change login, change UserLogin.
        ///</summary>
        public string UserLogin
        {
            get { return _userlogin; }
            set
            {
                if (_userlogin != value)
                {
                    if (OnPropertyChanging("UserLogin", _userlogin, value))
                    {
                        string oldValue = _userlogin;
                        _userlogin = value;
                        OnPropertyChanged("UserLogin", oldValue, value);
                    }
                }
            }
        }  private string _userlogin = null;

        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int UserLoginMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.UserLoginColumn.MaxLength);
            }
        }

        /// <summary>
        /// Hashed representation of the SubAdmin's Password. 
        ///</summary>
        public string Password
        {
            get { return _password; }
            set
            {
                if (_password != value)
                {
                    if (OnPropertyChanging("Password", _password, value))
                    {
                        string oldValue = _password;
                        _password = value;

                        if (SubAdmin.Current.IsAuthenticated)
                        {
                            if (UserID != SubAdmin.Current.UserID)
                                IsPasswordChangeRequired = false;
                        }

                        OnPropertyChanged("Password", oldValue, value);
                    }
                }
            }
        }   private string _password = null;


        /// <summary>
        /// Provides the Maximum Length in characters for this field.
        /// </summary>
        public static int PasswordMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.PasswordColumn.MaxLength);
            }
        }



        /// <summary>
        /// Type of Logged User.
        /// </summary>
        public string UserType
        {
            get { return _usertype; }
            set
            {
                if (_usertype != value)
                {
                    if (OnPropertyChanging("UserType", _usertype, value))
                    {
                        string oldValue = _usertype;
                        _usertype = value;
                        OnPropertyChanged("UserType", oldValue, value);
                    }
                }
            }
        }  private string _usertype = null;


        /// <summary>
        /// True iff the SubAdmin must change their password at next login.
        /// </summary>
        public bool IsPasswordChangeRequired
        {
            get { return _isPasswordChangeRequired; }
            set
            {
                if (_isPasswordChangeRequired != value)
                {
                    if (OnPropertyChanging("IsPasswordChangeRequired", _isPasswordChangeRequired, value))
                    {
                        bool oldValue = _isPasswordChangeRequired;
                        _isPasswordChangeRequired = value;
                        OnPropertyChanged("IsPasswordChangeRequired", oldValue, value);
                    }
                }
            }
        }  private bool _isPasswordChangeRequired = false;

        /// <summary>
        /// Date/time at which the User's password was last changed.
        /// </summary>
        public DateTime? LastPasswordChangedDate
        {
            get { return _lastPasswordChangedDate; }
            set
            {
                if (_lastPasswordChangedDate != value)
                {
                    if (OnPropertyChanging("LastPasswordChangedDate", _lastPasswordChangedDate, value))
                    {
                        DateTime? oldValue = _lastPasswordChangedDate;
                        _lastPasswordChangedDate = value;
                        OnPropertyChanged("LastPasswordChangedDate", oldValue, value);
                    }
                }
            }
        }  private DateTime? _lastPasswordChangedDate = null;


        /// <summary>
        /// SubAdmin's Login count.
        /// </summary>
        public Int32 LoginCount
        {
            get { return _loginCount; }
            set
            {
                if (_loginCount != value)
                {
                    if (OnPropertyChanging("LoginCount", _loginCount, value))
                    {
                        Int32 oldValue = _loginCount;
                        _loginCount = value;
                        OnPropertyChanged("LoginCount", oldValue, value);
                    }
                }
            }
        }  private Int32 _loginCount = 0;


        /// <summary>
        /// Date/time at which the User last logged-into the application.
        /// </summary>
        public DateTime? LastLoginDate
        {
            get { return _lastLoginDate; }
            set
            {
                if (_lastLoginDate != value)
                {
                    if (OnPropertyChanging("LastLoginDate", _lastLoginDate, value))
                    {
                        DateTime? oldValue = _lastLoginDate;
                        _lastLoginDate = value;
                        OnPropertyChanged("LastLoginDate", oldValue, value);
                    }
                }
            }
        }  private DateTime? _lastLoginDate = null;


        /// <summary>
        /// [Optional] Title of Courtesy (e.g. Mr., Mrs., Dr., etc)
        /// </summary>
        public string TitleOfCourtesy
        {
            get { return _titleOfCourtesy; }
            set
            {
                if (_titleOfCourtesy != value)
                {
                    if (OnPropertyChanging("TitleOfCourtesy", _titleOfCourtesy, value))
                    {
                        string oldValue = _titleOfCourtesy;
                        _titleOfCourtesy = value;
                        OnPropertyChanged("TitleOfCourtesy", oldValue, value);
                    }
                }
            }
        }  private string _titleOfCourtesy = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int TitleOfCourtesyMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.TitleOfCourtesyColumn.MaxLength);
            }
        }

        /// <summary>
        /// SubAdmin's First Name.
        /// </summary>
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                if (_firstName != value)
                {
                    if (OnPropertyChanging("FirstName", _firstName, value))
                    {
                        string oldValue = _firstName;
                        _firstName = value;
                        OnPropertyChanged("FirstName", oldValue, value);
                    }
                }
            }
        }  private string _firstName = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int FirstNameMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.FirstNameColumn.MaxLength);
            }
        }


        /// <summary>
        /// SubAdmin's Last Name.
        /// </summary>
        public string LastName
        {
            get { return _lastName; }
            set
            {
                if (_lastName != value)
                {
                    if (OnPropertyChanging("LastName", _lastName, value))
                    {
                        string oldValue = _lastName;
                        _lastName = value;
                        OnPropertyChanged("LastName", oldValue, value);
                    }
                }
            }
        }  private string _lastName = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int LastNameMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.LastNameColumn.MaxLength);
            }
        }

        /// <summary>
        /// SubAdmin's Email Address.
        /// </summary>
        public string EmailAddress
        {
            get { return _emailAddress; }
            set
            {
                if (_emailAddress != value)
                {
                    if (OnPropertyChanging("EmailAddress", _emailAddress, value))
                    {
                        string oldValue = _emailAddress;
                        _emailAddress = value;
                        OnPropertyChanged("EmailAddress", oldValue, value);
                    }
                }
            }
        }  private string _emailAddress = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int EmailAddressMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.EmailAddressColumn.MaxLength);
            }
        }


        /// <summary>
        /// SubAdmin's Work/Office Phone number.
        /// </summary>
        public string WorkPhone
        {
            get { return _workPhone; }
            set
            {
                if (_workPhone != value)
                {
                    if (OnPropertyChanging("WorkPhone", _workPhone, value))
                    {
                        string oldValue = _workPhone;
                        _workPhone = value;
                        OnPropertyChanged("WorkPhone", oldValue, value);
                    }
                }
            }
        }  private string _workPhone = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int WorkPhoneMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.WorkPhoneColumn.MaxLength);
            }
        }


        /// <summary>
        /// SubAdmin's Mobile Phone number.
        /// </summary>
        public string MobilePhone
        {
            get { return _mobilePhone; }
            set
            {
                if (_mobilePhone != value)
                {
                    if (OnPropertyChanging("MobilePhone", _mobilePhone, value))
                    {
                        string oldValue = _mobilePhone;
                        _mobilePhone = value;
                        OnPropertyChanged("MobilePhone", oldValue, value);
                    }
                }
            }
        }  private string _mobilePhone = null;


        /// <summary>
        /// Provides the Maximum Length in characters that the DAL will accept for this field.
        /// </summary>
        public static int MobilePhoneMaxLength
        {
            get
            {
                dsSubAdmin.SubAdminsDataTable dtDT = new dsSubAdmin.SubAdminsDataTable();
                return (dtDT.MobilePhoneColumn.MaxLength);
            }
        }


        /// <summary>
        /// PKID of the SubAdmin that created this SubAdmin's record.
        /// </summary>
        public int CreatedByID
        {
            get { return _createdByID; }
            set
            {
                if (_createdByID != value)
                {
                    if (OnPropertyChanging("CreatedByID", _createdByID, value))
                    {
                        int oldValue = _createdByID;
                        _createdByID = value;
                        //_createdByName = null;
                        OnPropertyChanged("CreatedByID", oldValue, value);
                    }
                }
            }
        }  private int _createdByID = 0;

        /// <summary>
        /// Date/time at which the SubAdmin was created.
        /// </summary>
        public DateTime? CreatedDate
        {
            get { return _createdDate; }
            set
            {
                if (_createdDate != value)
                {
                    if (OnPropertyChanging("CreatedDate", _createdDate, value))
                    {
                        DateTime? oldValue = _createdDate;
                        _createdDate = value;
                        OnPropertyChanged("CreatedDate", oldValue, value);
                    }
                }
            }
        }  private DateTime? _createdDate = null;

        /// <summary>
        /// PKID of the last SubAdmin to modify this SubAdmin's data.
        /// </summary>
        public int? LastModifiedByID
        {
            get { return _lastModifiedByID; }
            set
            {
                if (_lastModifiedByID != value)
                {
                    if (OnPropertyChanging("LastModifiedByID", _lastModifiedByID, value))
                    {
                        int? oldValue = _lastModifiedByID;
                        _lastModifiedByID = value;
                        //_lastModifiedByName = null;
                        OnPropertyChanged("LastModifiedByID", oldValue, value);
                    }
                }
            }
        }  private int? _lastModifiedByID = null;


        /// <summary>
        /// The last date this SubAdmin was modified.
        /// </summary>
        public DateTime? LastModifiedDate
        {
            get { return _lastModifiedDate; }
            set
            {
                if (_lastModifiedDate != value)
                {
                    if (OnPropertyChanging("LastModifiedDate", _lastModifiedDate, value))
                    {
                        DateTime? oldValue = _lastModifiedDate;
                        _lastModifiedDate = value;
                        OnPropertyChanged("LastModifiedDate", oldValue, value);
                    }
                }
            }
        }  private DateTime? _lastModifiedDate = null;


        /// <summary>
        /// True iff the SubAdmin has been authorized to access the application.
        /// </summary>
        public bool IsActive
        {
            get { return _isActive; }
            set
            {
                if (_isActive != value)
                {
                    if (OnPropertyChanging("IsActive", _isActive, value))
                    {
                        bool oldValue = _isActive;
                        _isActive = value;
                        OnPropertyChanged("IsActive", oldValue, value);
                    }
                }
            }
        }  private bool _isActive = false;

        /// <summary>
        /// True iff the SubAdmin has been Authenticated. [Read-only / Runtime-only / Not a database field]
        /// </summary>
        public bool IsAuthenticated
        {
            get { return _isAuthenticated; }
        }  private bool _isAuthenticated = false;



        /// <summary>
        /// Comma-separated, list of any RBS Roles to which the user has been assigned. 
        ///</summary>
        public string RolesSummary
        {
            get
            {
                if ((Roles == null) || (Roles.Count == 0))
                    return ("[none]");

                StringBuilder rolesSummary = new StringBuilder(256);
                for (int ndx = 0; ndx < Roles.Count; ndx++)
                {
                    if (ndx == 0)
                        rolesSummary.Append(Roles[ndx].RoleName);
                    else rolesSummary.AppendFormat(", {0}", Roles[ndx].RoleName);
                }
                return (rolesSummary.ToString());
            }
        }




        /// <summary>
        /// Property provided for convenience of UI development; not a database field.
        /// Fires OnPropertyChanging / OnPropertyChanged events.
        /// </summary>
        public bool IsSelected
        {
            get { return (_isSelected); }
            set
            {
                if (_isSelected != value)
                {
                    if (OnPropertyChanging("IsSelected", _isSelected, value))
                    {
                        bool oldValue = _isSelected;
                        _isSelected = value;
                        OnPropertyChanged("IsSelected", oldValue, value);
                    }
                }
            }
        } private bool _isSelected = false;


        #endregion

        #region Child-Collection

        /// <summary>
        /// Collection of all Roles to which this SubAdmin has been assigned.
        /// </summary>
        public SubAdminRoleCollection Roles
        {
            get
            {
                if (_roles == null)
                    _roles = SubAdminRole.GetRolesByUserID(UserID);

                return (_roles);
            }
            set { _roles = value; }
        } private SubAdminRoleCollection _roles = null;


        #endregion

        #endregion

        #region Public Methods

        #region CreateNew

        /// <summary>
        /// Creates a new, empty SubAdmin object.
        /// </summary>
        public static SubAdmin CreateNew()
        {
            SubAdmin obj = new SubAdmin();

            // Switch state to Added prior to initializing property values.
            obj.ObjState = ObjectState.Added;

            obj.UserGUID = Guid.NewGuid();
            obj.CreatedByID = SubAdmin.Current.UserID;
            obj.CreatedDate = DateTime.Now;
            obj.LoginCount = 0;
            obj.IsPasswordChangeRequired = true;
            obj.CreatedDate = DateTime.Now;
            obj.IsActive = true;

            return (obj);
        }


        [MethodImpl(MethodImplOptions.Synchronized)]
        public static SubAdmin CreateNew(SubAdminCollection collection)
        {
            SubAdmin obj = CreateNew();

            // Get next/unique ID for the new object.
            int minVal = 0;
            foreach (SubAdmin user in collection)
            {
                if (user.UserID < minVal)
                    minVal = user.UserID;
            }
            obj.UserID = --minVal;

            collection.Add(obj);

            return (obj);
        }

        #endregion

        #region GetAllUsers

        /// <summary>
        /// Returns a collection of all AppUsers.
        /// </summary>
        public static SubAdminCollection GetAllUsers()
        {
            SubAdminsTableAdapter adapter = new SubAdminsTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();

            dsSubAdmin.SubAdminsDataTable tbl = adapter.GetData(null);

            SubAdminCollection list = new SubAdminCollection();

            if (tbl.Rows.Count > 0)
            {
                foreach (dsSubAdmin.SubAdminsRow row in tbl.Rows)
                {
                    SubAdmin user = new SubAdmin();
                    RelationalToObject(user, row);
                    list.Add(user);
                }
            }

            return (list);
        }

        #endregion

        #region GetUserByUserLogin

        /// <summary>
        /// Returns an AppUser object for the specified 'userLogin' or null if no User with the specified parameter(s) exists.
        /// </summary>
        public static SubAdmin GetUserByUserLogin(string userLogin)
        {
            SubAdminsTableAdapter adapter = new SubAdminsTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();
            dsSubAdmin.SubAdminsDataTable tbl = adapter.GetDataByUserLogin(userLogin);

            if (tbl.Rows.Count == 1)
            {
                SubAdmin user = new SubAdmin();
                RelationalToObject(user, tbl.Rows[0]);
                return (user);
            }
            else if (tbl.Rows.Count > 1)
                throw new ApplicationException("Database integrity error: Specified 'userLogin' returned more than one User record.");

            return (null);
        }

        #endregion

        #region GetUserByEmailAddress

        ///// <summary>
        ///// Returns an AppUser object for the specified 'emailAddress' or null if no User with the specified parameter(s) exists.
        ///// </summary>
        //public static AppUser GetUserByEmailAddress(string emailAddress)
        //{
        //    AppUsersTableAdapter adapter = new AppUsersTableAdapter();
        //    adapter.Connection = AppSettings.GetDbConn();
        //    dsAppUsers.AppUsersDataTable tbl = adapter.GetDataBySearch(null, null, null, null, null, null, null,
        //                                                null, null, null, null, null, null, null, null, emailAddress, null, null,
        //                                                null, null, null, null, null, null);

        //    if (tbl.Rows.Count == 1)
        //    {
        //        AppUser user = new AppUser();
        //        RelationalToObject(user, tbl.Rows[0]);
        //        return (user);
        //    }
        //    else if (tbl.Rows.Count > 1)
        //        throw new ApplicationException("Database integrity error: Specified 'emailAddress' returned more than one User record.");

        //    return (null);
        //}

        #endregion

        #region LogonUser

        /// <summary>
        /// General logon user method. Upon successful logon, the caller of this method is expected to handle the redirect.
        /// ASSUMPTION: The 'userLogin' MUST be an email address.
        /// </summary>
        public static RbsError LogonUser(string userLogin, string clearTextPassword, out string errMsg)
        {
            errMsg = "";

            // Get the user from the database.
            SubAdmin user = SubAdmin.GetUserByUserLogin(userLogin);
            if (user == null)
            {
                // The UserLogin was not found.
                errMsg = "User not found.";
                return (RbsError.NO_RECORDS_FOUND);
            }

            // Check the User's account status to determine whether they are still active, not locked-out, etc.
            if (!user.IsActive)
            {
                errMsg = "This user account is not active.";
                return (RbsError.ACCOUNT_INACTIVE);
            }

            //if (user.Roles.Count == 0)
            //    return (RbsError.USER_NO_ROLES);

            if (clearTextPassword != null)
            {
                string hashedPswd = HashPassword(clearTextPassword);

                // ...for comparison to the DB-resident value.
                if (hashedPswd == user.Password)
                {
                    // Password change required?
                    if (user.IsPasswordChangeRequired)
                    {
                        errMsg = "Password change is required.";
                        return (RbsError.PASSWORD_CHANGE_REQUIRED);
                    }
                    else
                    {
                        // Set the AppUser singleton to the logged-on user.
                        if (!SubAdmin.SetCurrentAppUser(user, out errMsg))
                            return (RbsError.OTHER_EXCEPTION);

                        SubAdmin.IncrementLoginCount(SubAdmin.Current);

                        // Tell ASP.NET that this user is authenticated.
                        // Note: The caller of this method is expected to handle the redirect.
                        if (SessionMgr.IsWebApp)
                            System.Web.Security.FormsAuthentication.SetAuthCookie(userLogin, false);

                        user.SetIsAuthenticated(true);

                        errMsg = "";
                        return (RbsError.NO_ERROR);
                    }
                }
                else
                {
                    //user.FailedPasswordAttemptCount++;
                    //user.FailedPasswordAttemptWindowStart = DateTime.Now;
                    //AppUser.Update(user);

                    user.SetIsAuthenticated(false);

                    errMsg = "Invalid credentials.";
                    return (RbsError.INVALID_CREDENTIALS);
                }
            }
            else  // An external DB authorized this user.
            {
                // Set the AppUser singleton to the logged-on user.
                if (!SubAdmin.SetCurrentAppUser(user, out errMsg))
                    return (RbsError.OTHER_EXCEPTION);

                SubAdmin.IncrementLoginCount(SubAdmin.Current);

                // Tell ASP.NET that this user is authenticated.
                // Note: The caller of this method is expected to handle the redirect.
                System.Web.Security.FormsAuthentication.SetAuthCookie(userLogin, false);

                user.SetIsAuthenticated(true);

                errMsg = "";
                return (RbsError.NO_ERROR);
            }
        }

        #endregion

        #region ValidatePassword

        /// <summary>
        /// Validates the specified password against the app's password strength rules.
        /// </summary>
        public static RbsError ValidatePassword(string newPswd, out string errMsg)
        {
            errMsg = "";

            if (newPswd == null)
            {
                errMsg = "A password is required.";
                return (RbsError.PASSWORD_TOO_SHORT);
            }
            else if (newPswd.Length < APPUSER_DFLT_MINPSWDLEN)
            {
                errMsg = "Password does not meet the minimum length requirements.";
                return (RbsError.PASSWORD_TOO_SHORT);
            }

            return (RbsError.NO_ERROR);
        }

        /// <summary>
        /// Determines whether or not the password is "strong" as defined by the RBS subsystem.
        /// </summary>
        /// <param name="password">The password to be tested.</param>
        /// <returns>True if the password is "strong"; false otherwise.</returns>
        public static bool IsStrongPassword(string password)
        {
            if (HasPunctuation(password) && HasNumbers(password))
                return (true);
            else return (false);
        }


        /// <summary>
        /// Determines whether or not the string contains punctuation.
        /// </summary>
        /// <returns>True if the string contains one or more punctuation characters.</returns>
        private static bool HasPunctuation(string str)
        {
            // Regex includes '_' as a valid char for alphanumeric, so we
            // need to explicity state that it's considered punctuation.
            Regex rgxTmp = new Regex(@"[\W|_]");
            return (rgxTmp.IsMatch(str));
        }


        /// <summary>
        /// Determines whether or not the string contains numbers.
        /// </summary>
        /// <returns>True if the string contains one or more numbers.</returns>
        private static bool HasNumbers(string str)
        {
            Regex rgxTmp = new Regex(@"[\d]");
            return (rgxTmp.IsMatch(str));
        }


        /// <summary>
        /// Determines whether or not the string contains lower-case characters.
        /// </summary>
        /// <returns>True if the string contains one or more lower-case characters.</returns>
        private static bool HasLowerCaseChars(string str)
        {
            Regex rgxTmp = new Regex("[a-z]");
            return (rgxTmp.IsMatch(str));
        }


        /// <summary>
        /// Determines whether or not the string contains upper-case characters.
        /// </summary>
        /// <returns>True if the string contains one or more upper-case characters.</returns>
        private static bool HasUpperCaseChars(string str)
        {
            Regex rgxTmp = new Regex("[A-Z]");
            return (rgxTmp.IsMatch(str));
        }

        #endregion

        #region HashPassword

        /// <summary>
        /// Returns the hashed version of the clear-text password.
        /// </summary>
        public static string HashPassword(string clearTextPswd)
        {
            return (HashPassword(clearTextPswd, APPUSER_DFLT_PSWDSALT));
        }

        /// <summary>
        /// Returns the hashed version of the clear-text password.
        /// </summary>
        public static string HashPassword(string clearTextPswd, string salt)
        {
            // Append the salt value.
            string pswd = clearTextPswd + ((salt != null) ? salt : "");

            // Double-hash the password.
            pswd = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(pswd, "sha1");
            pswd = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(pswd, "sha1");

            return (pswd);
        }

        #endregion

        #region CreateRandomPassword

        public static string CreateRandomPassword(int passwordLength)
        {
            string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@$?_-";
            char[] chars = new char[passwordLength];
            Random rd = new Random();

            for (int ndx = 0; ndx < passwordLength; ndx++)
            {
                chars[ndx] = allowedChars[rd.Next(0, allowedChars.Length)];
            }

            return new string(chars);
        }

        #endregion

        #region ValidatePasswordEntry / ValidateNewPasswordEntry

        /// <summary>
        /// Method validates the password/confirmPassword parameters.
        /// </summary>
        public static bool ValidatePasswordEntry(string password, string confirmPassword, out string passwordErrMsg, out string confirmPasswordErrMsg)
        {
            passwordErrMsg = "";
            confirmPasswordErrMsg = "";

            if ((password == null) || (password.Trim().Length == 0))
            {
                passwordErrMsg = "Password is required. Please try again.";
                return (false);
            }

            if ((confirmPassword == null) || (confirmPassword.Trim().Length == 0))
            {
                confirmPasswordErrMsg = "Confirm Password is required. Please try again.";
                return (false);
            }

            if (password.Trim().Length < APPUSER_DFLT_MINPSWDLEN)
            {
                passwordErrMsg = String.Format("Password must be at least {0} characters in length. Please try again.", APPUSER_DFLT_MINPSWDLEN);
                return (false);
            }

            if (password.Trim().Length > SubAdmin.PasswordMaxLength)
            {
                passwordErrMsg = String.Format("Password must be less than {0} characters in length. Please try again.", SubAdmin.PasswordMaxLength);
                return (false);
            }

            if (confirmPassword.Trim().Length < APPUSER_DFLT_MINPSWDLEN)
            {
                confirmPasswordErrMsg = String.Format("Confirm Password must be at least {0} characters in length. Please try again.", APPUSER_DFLT_MINPSWDLEN);
                return (false);
            }

            if (confirmPassword.Trim().Length > SubAdmin.PasswordMaxLength)
            {
                confirmPasswordErrMsg = String.Format("Confirm Password must be less than {0} characters in length. Please try again.", SubAdmin.PasswordMaxLength);
                return (false);
            }

            if (password.Trim() != confirmPassword.Trim())
            {
                passwordErrMsg = "Passwords do not match. Please try again.";
                confirmPasswordErrMsg = "Passwords do not match. Please try again.";
                return (false);
            }

            return (true);
        }


        /// <summary>
        /// Method validates the oldPassword/newPassword/confirmPassword parameters.
        /// </summary>
        public static bool ValidateNewPasswordEntry(string oldPassword, string newPassword, string confirmPassword, out string newPasswordHash, out string passwordErrMsg, out string confirmPasswordErrMsg)
        {
            newPasswordHash = null;

            if (!ValidatePasswordEntry(newPassword, confirmPassword, out passwordErrMsg, out confirmPasswordErrMsg))
                return (false);

            if (oldPassword == SubAdmin.HashPassword(newPassword.Trim()))
            {
                passwordErrMsg = "Password must be different than current password. Please try again.";
                confirmPasswordErrMsg = "";
                return (false);
            }

            newPasswordHash = SubAdmin.HashPassword(newPassword.Trim());

            return (true);
        }

        #endregion

        #region Update

        /// <summary>
        /// Updates the specified SubAdmin.
        /// </summary>
        public static void Update(SubAdmin obj)
        {
            bool updateUser = false;
            bool updateRoles = false;

            // Do not update invalid objects.
            if (!obj.Validate())
            {
                StringBuilder sb = new StringBuilder(2048);
                sb.Append("The object being updated has one or more invalid properties; the Update() operation for this object has been cancelled.\nError(s):");
                foreach (ValidationError err in obj.Errors)
                    sb.Append("\n* " + err.Element + ": " + err.Message);

                throw new InvalidObjectException(sb.ToString());
            }
            else
            {
                updateUser = (obj.ObjState != ObjectState.Unchanged);

                foreach (SubAdminRole role in obj.Roles)
                {
                    if (role.ObjState != ObjectState.Unchanged)
                    {
                        updateRoles = true;
                        break;
                    }
                }

                if (!updateUser && !updateRoles)
                    return;
            }

            ObjectState originalObjectState = obj.ObjState;

            dsSubAdmin.SubAdminsDataTable tbl = null;
            if (updateUser)
            {
                obj.SuppressClearRolesOnUserIDChanged = (obj.ObjState == ObjectState.Added);

                SubAdminsTableAdapter adapter = new SubAdminsTableAdapter();
                adapter.Connection = AppSettings.GetDbConn();


                tbl = new dsSubAdmin.SubAdminsDataTable();
                dsSubAdmin.SubAdminsRow row = tbl.NewSubAdminsRow();
                ObjectToRelational(obj, tbl, row);

                adapter.Update(tbl);

                if (originalObjectState == ObjectState.Deleted)
                    obj = null;
                else RelationalToObject(obj, tbl.Rows[0]);
            }

            if (updateRoles)
            {
                if (originalObjectState == ObjectState.Added)
                    foreach (SubAdminRole role in obj.Roles)
                        role.UserID = obj.UserID;

                SubAdminRole.Update(obj.Roles);
            }
        }


        /// <summary>
        /// Updates the specified list of AppUsers.
        /// </summary>
        public static void Update(SubAdminCollection coll)
        {
            SubAdminsTableAdapter adapter = new SubAdminsTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();
            dsSubAdmin.SubAdminsDataTable tbl = new dsSubAdmin.SubAdminsDataTable();

            foreach (SubAdmin obj in coll)
            {
                // Do not update invalid objects.
                if (!obj.Validate())
                {
                    StringBuilder sb = new StringBuilder(2048);
                    sb.Append("The object being updated has one or more invalid properties; the Update() operation for this object has been cancelled.\nError(s):");
                    foreach (ValidationError err in obj.Errors)
                        sb.Append("\n* " + err.Element + ": " + err.Message);

                    throw new InvalidObjectException(sb.ToString());
                }

                dsSubAdmin.SubAdminsRow row = tbl.NewSubAdminsRow();
                ObjectToRelational(obj, tbl, row);
            }

            adapter.Update(tbl);

            // Remove deleted objects.
            for (int ndx = 0; ndx < coll.Count; ndx++)
            {
                if (coll[ndx].ObjState == ObjectState.Deleted)
                {
                    coll.RemoveAt(ndx);
                    ndx--;
                }
            }

            // Update any Added or Modified objects.
            foreach (dsSubAdmin.SubAdminsRow row in tbl.Rows)
            {
                SubAdmin obj = coll.FindByUserID(row.UserID);

                if (obj == null)
                    obj = coll.FindByUserGUID(row.UserGUID);

                RelationalToObject(obj, row);
            }
        }

        #endregion

        #region Validate

        /// <summary>
        /// Validates the object based on its properties at the time of the call; updates the Errors property.
        /// </summary>
        public override bool Validate()
        {
            Errors.Clear();

            if (ObjState == ObjectState.Unchanged)
            {
                // By definition, unchanged objects are valid.
                return (true);
            }
            else if (ObjState == ObjectState.Uninitialized)
            {
                Errors.Add(new ValidationError("object", "Object is uninitialized.", ValidationError.Severity.Error));

                // No need to proceed past this error.
                return (false);
            }
            else if ((ObjState == ObjectState.Deleted) && (UserID <= 0))
            {
                // Deleted items must have 'real' (i.e. non-negative, non-zero) key(s) to be valid.
                Errors.Add(new ValidationError("UserID", "Deleted object has an invalid PKID.", ValidationError.Severity.Error));

                // No need to proceed past this error.
                return (false);
            }
            else  // ObjState == Added | Modified
            {

                // UserName is required.
                if ((UserLogin == null) || (UserLogin.Trim().Length == 0))
                {
                    Errors.Add(new ValidationError("UserLogin", "UserLogin is required.", ValidationError.Severity.Error));
                }
                else if (ObjState == ObjectState.Added)
                {
                    // Ensure that the UserName is unique within the associated Retailer.
                    //if (AppUser.GetUserByUsernameRetailerID(UserLoginID, RetailerID) != null)
                    //{
                    //    string retailerName = Retailer.DisplayName;
                    //    Errors.Add(new ValidationError("UserName", "Username must be unique; \"" + UserLoginID + "\" is already present in Retailer \"" + custName + "\".", ValidationError.Severity.Error));
                    //}
                }

                // TODO: Check the Company's AuthMethod: If it's PKID_LocalDB, then Password is required.

                // FirstName is required.
                if ((FirstName == null) || (FirstName.Length == 0))
                    Errors.Add(new ValidationError("FirstName", "FirstName is required.", ValidationError.Severity.Error));

                // LastName is required.
                if ((LastName == null) || (LastName.Length == 0))
                    Errors.Add(new ValidationError("LastName", "LastName is required.", ValidationError.Severity.Error));

                // TODO: Regex validate the email address format.

                // CreatedByID is required.
                if (CreatedByID <= 0)
                    CreatedByID = SubAdmin.Current.UserID;

                // Ensure LastModifiedBy is current.
                if (ObjState == ObjectState.Modified)
                    LastModifiedByID = SubAdmin.Current.UserID;
            }

            return (Errors.Count == 0);
        }

        #endregion

        #region ToString

        /// <summary>
        /// User-friendly string represetation of this object.
        /// </summary>
        public override string ToString()
        {
            return (String.Format("SubAdmin: PKID={0}, User={1}, {2}", UserID, LastName, FirstName));
        }

        #endregion

        #endregion 

        #region Privates

        #region SetCurrentAppUser

        /// <summary>
        /// Sets the Current property to the specified AppUser object.
        /// </summary>
        private static bool SetCurrentAppUser(SubAdmin user, out string errMsg)
        {
            errMsg = "";

            lock (typeof(SubAdmin))
            {
                SessionMgr.Session[PROP_CURRENT] = user;
            }

            // Load the appropriate navigation/menu items for AppUser.Current.
            //Navigation.ClearCachedNavData();

            return (true);
        }

        #endregion

        #region IncrementLoginCount

        /// <summary>
        /// Method increments the LoginCount property of the specified 'user' and invokes a
        /// stored-procedure to store the new count without changing the LastModifiedByID and
        /// LastModifiedDate fields of the corresponding User row in the database.
        /// </summary>
        private static void IncrementLoginCount(SubAdmin user)
        {
            user.LoginCount++;

            SubAdminsTableAdapter adapter = new SubAdminsTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();
            dsSubAdmin.SubAdminsDataTable tbl = new dsSubAdmin.SubAdminsDataTable();

            dsSubAdmin.SubAdminsRow row = tbl.NewSubAdminsRow();
            ObjectToRelational(user, tbl, row);

            adapter.UpdateLoginCount(tbl, user.UserID, user.LoginCount);

            RelationalToObject(user, tbl.Rows[0]);
        }

        #endregion

        #region SetIsAuthenticated

        internal void SetIsAuthenticated(bool isAuthenticated)
        {
            _isAuthenticated = isAuthenticated;
        }

        #endregion

        #endregion 

    }

    /// <summary>
    /// Collection of 0 or more SubAdmin objects.
    /// </summary>

    public class SubAdminCollection : List<SubAdmin>
    {
        #region Types

        public class UserComparer : IComparer<SubAdmin>
        {
            public int Compare(SubAdmin user1, SubAdmin user2)
            {
                return (String.Compare(user1.LastName, user2.LastName));
            }
        }

        #endregion

        #region Public Methods

        #region FindByUserID

        public SubAdmin FindByUserID(int userID)
        {
            for (int ndx = 0; ndx < this.Count; ndx++)
                if (this[ndx].UserID == userID)
                    return (this[ndx]);

            return (null);
        }

        #endregion

        #region FindByUserGUID

        public SubAdmin FindByUserGUID(Guid userGUID)
        {
            for (int ndx = 0; ndx < this.Count; ndx++)
                if (this[ndx].UserGUID == userGUID)
                    return (this[ndx]);

            return (null);
        }

        #endregion

        #region FindUserByUserLogin

        /// <summary>
        /// Case insensitve compare.
        /// </summary>
        public SubAdmin FindUserByUserLogin(string userLogin)
        {
            for (int ndx = 0; ndx < this.Count; ndx++)
                if (this[ndx].UserLogin == userLogin)
                    return (this[ndx]);

            return (null);
        }

        #endregion

        #region FindByFirstLastName

        public SubAdmin FindByFirstLastName(string firstName, string lastName)
        {
            for (int ndx = 0; ndx < this.Count; ndx++)
                if ((this[ndx].FirstName == firstName) && (this[ndx].LastName == lastName))
                    return (this[ndx]);

            return (null);
        }

        #endregion

        #region SortListByLastName

        /// <summary>
        /// Sorts the collection alpha ascending by Last Name.
        /// </summary>
        public void SortListByLastName()
        {
            this.Sort(new UserComparer());
        }

        #endregion

        #region GetDistinct

        public SubAdminCollection GetDistinct()
        {
            SubAdminCollection retVal = new SubAdminCollection();
            List<int> list = new List<int>();

            foreach (SubAdmin item in this)
            {
                if (!list.Contains(item.UserID))
                    list.Add(item.UserID);
            }

            foreach (int id in list)
                retVal.Add(this.FindByUserID(id));

            return retVal;
        }

        #endregion


        #endregion 

    }

    ///<summary>
    /// This class represents the unordered tuple of roles (SubAdmin, Role)
    /// </summary>
    public class SubAdminRole : BOBase
    { 

    	#region Construction / Disposal

		/// <summary>
		/// Default constructor.
		/// </summary>
        protected SubAdminRole() { }

		#endregion

        #region Public Properties

        /// <summary>
        /// PKID of the SubAdmin to whom this role applies.
        /// </summary>
        public int UserID
        {
            get { return (_userID); }
            set
            {
                if (OnPropertyChanging("UserID", _userID, value))
                {
                    int oldValue = _userID;
                    _userID = value;
                    _userName = null;
                    OnPropertyChanged("UserID", oldValue, value);
                }
            }
        } private int _userID = 0;


        /// <summary>
        /// The PKID of this role in table LU_UserRoles.
        /// </summary>
        public int RBSRoleID
        {
            get { return (_rbsRoleID); }
            set
            {
                if (OnPropertyChanging("RBSRoleID", _rbsRoleID, value))
                {
                    object oldValue = _rbsRoleID;
                    _rbsRoleID = value;
                    _roleName = null;
                    OnPropertyChanged("RBSRoleID", oldValue, value);
                }
            }
        } private int _rbsRoleID = 0;


        /// <summary>
        /// The Role name (i.e. LU_UserRoles.RoleName).
        /// </summary>
        public string RoleName
        {
            get
            {
                if ((_roleName == null) && (RBSRoleID > 0))
                    _roleName = LookupCacheManager.RbsRoles.TextFromValue(RBSRoleID);

                return (_roleName);
            }
            private set { _roleName = value; }
        } private string _roleName = null;


        /// <summary>
        /// The [Last Name, First Name] of the person referenced by the UserID property.
        /// </summary>
        public string UserName
        {
            get
            {
                if (_userName == null)
                    _userName = LookupCacheManager.Users.UserLastNameFirstNameFromUserID(UserID);

                return (_userName);
            }
        } private string _userName = null;


        /// <summary>
        /// Property provided for convenience of UI development; not a database field.
        /// Does NOT fire OnPropertyChanging / OnPropertyChanged events.
        /// </summary>
        public bool IsModifiable
        {
            get { return (_isModifiable); }
            set { _isModifiable = value; }
        } private bool _isModifiable = true;


        #endregion

        #region Public Methods

        #region CreateNew

        /// <summary>
        /// Creates a new, empty AppUser object.
        /// </summary>
        public static SubAdminRole CreateNew()
        {
            SubAdminRole obj = new SubAdminRole();
            obj.ObjState = ObjectState.Added;

            // TODO: Provide any other default values for required fields.

            return (obj);
        }


        /// <summary>
        /// Creates a new AppUserRole object.
        /// </summary>
        public static SubAdminRole CreateNew(int userID, Schema.UserRoles rbsRoleID)
        {
            SubAdminRole obj = new SubAdminRole();
            obj.ObjState = ObjectState.Added;

            obj.UserID = userID;
            obj.RBSRoleID = (int)rbsRoleID;

            return (obj);
        }

        #endregion

        #region SetDeleted

        /// <summary>
        /// Removes the specified User from the database.
        /// </summary>
        public static void SetDeleted(SubAdminRole userRole)
        {
            // TODO: Rule check to see if User Role can be deleted (e.g. no related records still in the database.)
            userRole.ObjState = ObjectState.Deleted;
        }

        #endregion

        #region GetAllRoles

        public static SubAdminRoleCollection GetAllRoles(bool includeInactives)
        {
            SubAdminRoleCollection coll = new SubAdminRoleCollection();

            // Pull the roles from the Lookup cache.
            List<RbsRolesElement> lookupElements = LookupCacheManager.RbsRoles.Select(ElementFilters.Active | (includeInactives ? ElementFilters.Inactive : 0));
            foreach (RbsRolesElement obj in lookupElements)
            {
                SubAdminRole role = SubAdminRole.CreateNew();
                role.RBSRoleID = obj.LU_RBSRoleID;
                role.RoleName = obj.RoleName;
                coll.Add(role);
            }

            return (coll);
        }

        #endregion

        #region GetRolesByUserID

        /// <summary>
        /// Returns a collection of AppUserRole objects for the specified UserID.
        /// </summary>
        public static SubAdminRoleCollection GetRolesByUserID(int userID)
        {
            xref_SubAdmin_RBSRolesTableAdapter adapter = new xref_SubAdmin_RBSRolesTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();

            dsSubAdmin.xref_SubAdmin_RBSRolesDataTable tbl = adapter.GetDataByID(userID, null);

            SubAdminRoleCollection list = new SubAdminRoleCollection();

            if (tbl.Rows.Count > 0)
            {
                foreach (dsSubAdmin.xref_SubAdmin_RBSRolesRow row in tbl.Rows)
                {
                    SubAdminRole role = new SubAdminRole();
                    RelationalToObject(role, row);
                    list.Add(role);
                }
            }

            return (list);
        }

        #endregion

        #region GetRolesByRolesID

        /// <summary>
        /// Returns a collection of SubAdminRole objects for the specified RoleID.
        /// </summary>
        public static SubAdminRoleCollection GetRolesByRolesID(int roleID)
        {
            xref_SubAdmin_RBSRolesTableAdapter adapter = new xref_SubAdmin_RBSRolesTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();

            dsSubAdmin.xref_SubAdmin_RBSRolesDataTable tbl = adapter.GetDataByID(null, roleID);

            SubAdminRoleCollection list = new SubAdminRoleCollection();

            if (tbl.Rows.Count > 0)
            {
                foreach (dsSubAdmin.xref_SubAdmin_RBSRolesRow row in tbl.Rows)
                {
                    SubAdminRole role = new SubAdminRole();
                    RelationalToObject(role, row);
                    list.Add(role);
                }
            }

            return (list);
        }

        #endregion

        #region Update

        /// <summary>
        /// Updates the specified list of AppUserRoles by deleting the corresponding xref_AppUsers_RBSRoles records for each
        /// distinct UserID found in the collection of 'roles' and then inserting a record for each object in 'roles'.
        /// IMPORTANT: Parameter 'roles' must contain the complete set of role objects for each distinct UserID found
        /// in the collection 'roles'.
        /// </summary>
        public static void Update(SubAdminRoleCollection roles)
        {
            if (roles == null)
                return;

            // Validate the objects and create a table of added/insertable xref_User_HE_Role rows.
            foreach (SubAdminRole obj in roles)
            {
                // Do this so Validate() looks at the object as if it will be added/inserted.
                obj.ObjState = ObjectState.Added;

                if (!obj.Validate())
                {
                    StringBuilder sb = new StringBuilder(2048);
                    sb.Append("The object being updated has one or more invalid properties; the Update() operation for this object has been cancelled.\nError(s):");
                    foreach (ValidationError err in obj.Errors)
                        sb.Append("\n* " + err.Element + ": " + err.Message);

                    throw new InvalidObjectException(sb.ToString());
                }
            }

            // Get list of distinct UserIDs.
            List<int> userIDs = roles.GetDistinctUserIDs();

            SqlTransaction trans = null;
            SqlConnection conn = AppSettings.GetDbConn();
            xref_SubAdmin_RBSRolesTableAdapter adapter;
            try
            {
                adapter = new xref_SubAdmin_RBSRolesTableAdapter();
                adapter.Connection = conn;
                trans = TableAdapterHelper.BeginTransaction(adapter);

                foreach (int userID in userIDs)
                    adapter.Delete(userID, null);

                foreach (SubAdminRole role in roles)
                    adapter.Insert(role.UserID, role.RBSRoleID);

                trans.Commit();
            }
            catch (Exception ex)
            {
                string msg = ex.Message;

                trans.Rollback();
                throw;
            }
            finally
            {
                conn.Close();
                trans.Dispose();
            }

            // For consistency with the way the rest of the Update()'s work, reset the object states to Unchanged.
            foreach (SubAdminRole role in roles)
                role.ObjState = ObjectState.Unchanged;
        }

        #endregion

        #region ClearUserRoles

        /// <summary>
        /// Method removes all roles for the specified 'userID'.
        /// </summary>
        public static void ClearUserRoles(int userID)
        {
            xref_SubAdmin_RBSRolesTableAdapter adapter = new xref_SubAdmin_RBSRolesTableAdapter();
            adapter.Connection = AppSettings.GetDbConn();
            adapter.Delete(userID, null);
        }

        #endregion

        #region Validate

        /// <summary>
        /// Validates the object based on its properties at the time of the call; updates the Errors property.
        /// </summary>
        public override bool Validate()
        {
            Errors.Clear();

            if (ObjState == ObjectState.Unchanged)
            {
                // By definition, unchanged objects are valid.
                return (true);
            }
            else if (ObjState == ObjectState.Uninitialized)
            {
                Errors.Add(new ValidationError("object", "Object is uninitialized.", ValidationError.Severity.Error));

                // No need to proceed past this error.
                return (false);
            }
            else if ((ObjState == ObjectState.Deleted) && (UserID <= 0))
            {
                // Deleted items must have 'real' (i.e. non-negative, non-zero) key(s) to be valid.
                Errors.Add(new ValidationError("UserID", "Deleted object has an invalid PKID.", ValidationError.Severity.Error));

                // No need to proceed past this error.
                return (false);
            }
            else  // ObjState == Added | Modified
            {
                // UserID is required.
                if (UserID <= 0)
                    Errors.Add(new ValidationError("UserID", "A valid UserID is required.", ValidationError.Severity.Error));

                // UserRoleID is required.
                if (RBSRoleID <= 0)
                    Errors.Add(new ValidationError("RBSRoleID", "A valid RBSRoleID is required.", ValidationError.Severity.Error));
            }

            return (Errors.Count == 0);
        }

        #endregion

        #region ToString

        /// <summary>
        /// User-friendly string represetation of this object.
        /// </summary>
        public override string ToString()
        {
            return (String.Format("SubAdminRole: UserName={0}, UserID={1}, RoleName={2}, RBSRoleID={3}, ObjState={4}]",
                UserName, UserID, RoleName, RBSRoleID, ObjState.ToString()));
        }

        #endregion

        #endregion 
        
        #region Privates - None
        #endregion
    }

    /// <summary>
    /// Collection of SubAdminRole objects. Used to describe the set of Roles associated with a particular User.
    /// </summary>
    public class SubAdminRoleCollection : List<SubAdminRole>
    {
        #region Types

        public class RoleNameComparerASC : IComparer<SubAdminRole>
        {
            public int Compare(SubAdminRole obj1, SubAdminRole obj2)
            {
                return (String.Compare(obj1.RoleName, obj2.RoleName));
            }
        }


        public class RoleNameComparerDESC : IComparer<SubAdminRole>
        {
            public int Compare(SubAdminRole obj1, SubAdminRole obj2)
            {
                return (String.Compare(obj2.RoleName, obj1.RoleName));
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// List of the roles to which the User is assigned.
        /// </summary>
        public string[] RolesList
        {
            get
            {
                ArrayList roles = new ArrayList(8);
                foreach (SubAdminRole role in this)
                    roles.Add(role.RoleName);

                return ((string[])roles.ToArray(typeof(string)));
            }
        }

        #endregion

        #region Public Methods

        #region IsInRole

        /// <summary>
        /// Return true iff the User is in the specified role.
        /// </summary>
        public bool IsInRole(int roleID)
        {
            return (IsInRole((Schema.UserRoles)roleID));
        }


        /// <summary>
        /// Return true iff the User is in the specified role.
        /// </summary>
        public bool IsInRole(Schema.UserRoles rbsRoleID)
        {
            foreach (SubAdminRole role in this)
                if ((Schema.UserRoles)role.RBSRoleID == rbsRoleID)
                    return (true);

            return (false);
        }

        #endregion

        #region FindByRoleID

        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'roleID'.
        /// </summary>
        public SubAdminRoleCollection FindByRoleID(int roleID)
        {
            return (FindByRoleID((Schema.UserRoles)roleID));
        }


        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'roleID'.
        /// </summary>
        public SubAdminRoleCollection FindByRoleID(Schema.UserRoles rbsRoleID)
        {
            SubAdminRoleCollection coll = new SubAdminRoleCollection();

            for (int ndx = 0; ndx < this.Count; ndx++)
                if (this[ndx].RBSRoleID == (int)rbsRoleID)
                    coll.Add(this[ndx]);

            return (coll);
        }

        #endregion

        #region FindByUserID

        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'userID'.
        /// </summary>
        public SubAdminRoleCollection FindByUserID(int userID)
        {
            SubAdminRoleCollection coll = new SubAdminRoleCollection();

            for (int ndx = 0; ndx < this.Count; ndx++)
                if (this[ndx].UserID == userID)
                    coll.Add(this[ndx]);

            return (coll);
        }

        #endregion

        #region FindByUserIDRoleID

        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'userID' and 'roleID'.
        /// </summary>
        public SubAdminRole FindByUserIDRoleID(int userID, string strRoleID)
        {
            if (strRoleID == null)
                return (null);

            int roleID;
            if (!Int32.TryParse(strRoleID, out roleID))
                return (null);

            return (FindByUserIDRoleID(userID, (Schema.UserRoles)roleID));
        }


        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'userID' and 'roleID'.
        /// </summary>
        public SubAdminRole FindByUserIDRoleID(int userID, int roleID)
        {
            return (FindByUserIDRoleID(userID, (Schema.UserRoles)roleID));
        }


        /// <summary>
        /// Returns an SubAdminRoleCollection of objects having the specified 'userID' and 'roleID'.
        /// </summary>
        public SubAdminRole FindByUserIDRoleID(int userID, Schema.UserRoles rbsRoleID)
        {
            for (int ndx = 0; ndx < this.Count; ndx++)
                if ((this[ndx].UserID == userID) && (this[ndx].RBSRoleID == (int)rbsRoleID))
                    return (this[ndx]);

            return (null);
        }

        #endregion

        #region GetDistinctUserIDs

        /// <summary>
        /// Method returns an integer list of the distinct UserIDs in this collection of SubAdminRoles.
        /// </summary>
        public List<int> GetDistinctUserIDs()
        {
            List<int> userIDs = new List<int>(8);

            foreach (SubAdminRole role in this)
            {
                if (!userIDs.Contains(role.UserID))
                    userIDs.Add(role.UserID);
            }

            return (userIDs);
        }

        #endregion

        #region SortByRoleNameAsc

        public SubAdminRoleCollection SortByRoleNameAsc()
        {
            this.Sort(new RoleNameComparerASC());
            return (this);
        }

        #endregion

        #region SortByRoleNameDesc

        public SubAdminRoleCollection SortByRoleNameDesc()
        {
            this.Sort(new RoleNameComparerDESC());
            return (this);
        }

        #endregion

        #region GetPermittedRolesByUserID - TODO

        /// <summary>
        /// Returns a collection of all AppUserRoles that could potentially be assigned to the specified User.
        /// </summary>
        public static SubAdminRoleCollection GetPermittedRolesByUserID(int userID)
        {
            //bool allowSpecialRoles = false;
            SubAdminRoleCollection list = new SubAdminRoleCollection();
            // To Do...
            return (list);
        }

        #endregion

        #region ToString

        /// <summary>
        /// User-friendly string representation of this object.
        /// </summary>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(2048);
            foreach (object obj in this)
                sb.AppendFormat("{0}\n", obj.ToString());

            return (sb.ToString());
        }

        #endregion

        #endregion

        #region Privates - None
        #endregion
    
    }


}
